Skip to content

feat(wallet): actually run the federation server stellar.toml advertises - #1330

Merged
Miracle656 merged 1 commit into
mainfrom
feat/federation-server
Nov 8, 2026
Merged

Miracle656 merged 1 commit into
mainfrom
feat/federation-server

Conversation

@Miracle656

Copy link
Copy Markdown
Owner

public/.well-known/stellar.toml has been publishing

FEDERATION_SERVER = "https://app.useveilapp.xyz/api/federation"

to anyone who looked, while that route did not exist. Every attempt to resolve
a Veil name got Next's HTML 404 where a wallet expected JSON — Veil was naming
a federation server it did not run. Verified live: that URL returns 404 today.

What this adds

The SEP-2 endpoint and the resolver behind it.

type behaviour why
name reads resolve(name) off contracts/veil-names the one that matters, and what lib/federation.ts sends
id 404 the registry is keyed name → address with no reverse index; answering would mean scanning every name
forward 400, not 404 the spec reserves it for an off-network rail and Veil runs none, so "not found" would imply a right destination might exist
txid 404 Veil keeps no federation-side transaction records

Expiry needs no handling here: the contract already returns None past it,
which is what makes a lapsed name stop resolving rather than quietly paying
whoever buys it next.

The registry contract is not deployed (#1328), so every lookup is a truthful
404 today. The shape is the point — a wallet that handles this endpoint now
keeps working the moment the registry is live, and nothing invents an answer.
getNamesContractId() deliberately has no baked-in default, unlike the factory
id, because a placeholder would make the module speak with confidence about a
contract that does not exist.

Two choices worth stating

  • Names are shape-checked before any RPC, mirroring validate_shape in the
    contract. A short-circuit, not a control — the contract re-checks, since
    anyone can invoke it directly — but it stops a junk query costing a metered
    QuickNode call. A Cyrillic homoglyph is refused rather than folded to ASCII:
    folding would let one name resolve to another holder's payment destination.
  • An RPC failure is a 404, never a 500. lib/federation.ts distinguishes
    404 ("address not found") from anything else ("server unreachable"). A server
    that 500s on a hiccup teaches callers both that the name does not resolve and
    that Veil is unreliable; only the first is true.

⚠ Known, recorded in the module header rather than fixed

A Veil name resolves to a C… address, and SEP-2 documents account_id as a
Stellar public key. A classic payment cannot address a contract, and CAP-84
W… addresses do not help — a muxed contract address is not a classic
destination either. So a Veil name is payable from Veil and from anything
speaking Soroban, and not from an exchange.

Returning the contract address is still correct: that is where the money is,
and inventing a G… would be worse. But it must be settled before a name is
marketed as an address you can give anyone. See docs/IDENTIFIER_ADDRESSES.md.

Verification

  • npx tsc --noEmit — clean
  • 32 new tests; full wallet suite 102 suites / 1596 passed

Pre-existing CI failures, not from this branch

SDK — typecheck, test & size budget and Security Audit — cargo audit fail on
every current PR including frontend-only ones. The SDK one is a real budget
breach on main: 235.18 kB against a 235 kB limit, over by 175 B. Worth its
own fix rather than silently raising the limit.

https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB

`public/.well-known/stellar.toml` has been publishing

    FEDERATION_SERVER = "https://app.useveilapp.xyz/api/federation"

to anyone who looked, while that route did not exist. Every attempt to resolve
a Veil name got Next's HTML 404 where a wallet expected JSON, so Veil was
naming a federation server it did not run. The repo's own comment in the toml
admitted the URL was aspirational — but the file is deployed.

Adds the SEP-2 endpoint and the resolver behind it:

- `type=name` reads `resolve(name)` off `contracts/veil-names`. Expiry needs
  no handling: the contract already returns `None` past it, which is what
  makes a lapsed name stop resolving instead of quietly paying whoever buys it
  next.
- `type=id` answers 404 by design. The registry is keyed name -> address with
  no reverse index, so answering would mean scanning every name.
  `lib/reverseResolution.ts` already degrades to null and renders the raw
  address.
- `type=forward` is a 400, not a 404. The spec reserves it for handing a
  payment to an off-network rail and Veil runs none, so "not found" would
  imply a right destination might exist.
- `type=txid` is a 404.

The registry contract is not deployed, so every lookup is a truthful 404
today. The shape is the point: a wallet that handles this endpoint now keeps
working the moment the registry is live, and nothing here invents an answer.
`getNamesContractId()` has deliberately no baked-in default, unlike the
factory id, because a placeholder would make the module speak with confidence
about a contract that does not exist.

Two deliberate choices worth stating:

- Names are shape-checked here against `validate_shape` in the contract before
  any RPC. It is a short-circuit, not a control — the contract re-checks,
  because anyone can invoke it directly — but it keeps a junk query from
  costing a metered QuickNode call. A Cyrillic homoglyph is refused rather
  than folded to ASCII; folding would let one name resolve to another holder's
  payment destination.
- An RPC failure resolves to a 404, never a 500. `lib/federation.ts`
  distinguishes 404 ("address not found") from anything else ("server
  unreachable"), and a server that 500s on a hiccup teaches callers both that
  the name does not resolve and that Veil is unreliable. Only the first is
  true.

⚠ Recorded in the module header rather than fixed: a Veil name resolves to a
`C…` address, and SEP-2 documents `account_id` as a Stellar public key. A
classic `payment` cannot address a contract, and CAP-84 `W…` addresses do not
help because a muxed contract address is not a classic destination either. So
a Veil name is payable from Veil and from anything speaking Soroban, and not
from an exchange. Returning the contract address is still right — that is
where the money is, and inventing a `G…` would be worse — but it has to be
settled before a name is marketed as an address you can give anyone. See
docs/IDENTIFIER_ADDRESSES.md.

`npx tsc --noEmit` clean; 32 new tests.

Claude-Session: https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB
@vercel

vercel Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
veil Ready Ready Preview Nov 8, 2026 9:06am UTC
veil-2ap8 Ready Ready Preview Nov 8, 2026 9:06am UTC
veil-ezry Error Error Nov 8, 2026 9:06am UTC

@github-actions

github-actions Bot commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

❌ E2E Tests Failed

Playwright tests failed. Check the uploaded artifacts for:

  • Screenshots of failures
  • Trace files for debugging
  • Full test report

View workflow run

@Miracle656
Miracle656 merged commit 5592a60 into main Nov 8, 2026
13 of 18 checks passed
@Miracle656
Miracle656 deleted the feat/federation-server branch November 8, 2026 09:22

This branch had an error being deployed

1 failed and 2 active deployments
Preview – veil-ezry — 3bb97be0 Deployed Nov 8, 2026 by vercel[bot]
Preview – veil-2ap8 — 3bb97be0 Deployed Nov 8, 2026 by vercel[bot]
Preview – veil — 3bb97be0 Deployed Nov 8, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant